// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of DOM3XPath
//
// DOM3XPath is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// DOM3XPath is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with DOM3XPath. If not, see <http://www.gnu.org/licenses/>.
//	
//
//   $Id$
// XPathExpression.cpp : Implementation of CXPathExpression

//TODO: move addFunctionLibrary to Evaluator.

#include "stdafx.h"
#include <DOM3XPath.h>
#include "XPathExpression.h"
#include "XPathResult.h"
#include <comdef.h>
#include "XPathHelper.h"
#include <stdio.h>
#include <atlstr.h>
#define E_NOCONTEXTNODE E_FAIL
#define E_NOXPATH E_FAIL
#define E_NOTINITIALISED E_FAIL
/////////////////////////////////////////////////////////////////////////////
// CXPathExpression

STDMETHODIMP CXPathExpression::InterfaceSupportsErrorInfo(REFIID riid)
{
	static const IID* arr[] = 
	{
		&IID_IXPathExpression
	};
	for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		if (::InlineIsEqualGUID(*arr[i],riid))
			return S_OK;
	}
	return S_FALSE;
}

const CComBSTR sNsPropName("SelectionNamespaces");

STDMETHODIMP CXPathExpression::evaluate(IXMLDOMNode *contextNode, short type, IXPathResult *result, IXPathResult ** retVal)
{
	HRESULT hr;
	if(!contextNode)
	{
		retVal = NULL;
		hr = E_NOCONTEXTNODE;
	}
	else if(!m_pProcessor && !m_sExpression)
	{
		hr = E_NOTINITIALISED;
	}
	else
	{
		CComObject<CXPathResult> * pResult = NULL;

		if(result)
		{
			pResult = (CComObject<CXPathResult>*)result;
		}
		else
		{
			CComObject<CXPathResult>::CreateInstance(&pResult);
			pResult->AddRef();
		}
		pResult->m_uResultType =(CXPathResult::EXPathResultType) type;
		if(!m_pProcessor)
		{
			//Unfortunately, selectNodes can only cope if the expression returns
			//	a nodeset.  Although some expressions desiring other datatypes
			//	will also return a nodeset (e.g. string_type, returning first-text-child of first-node)
			//  it is not guaranteed.
			//	Therefore, we still need to use the cached stylesheet method, even though no
			//	functions are being called.

			switch(pResult->m_uResultType)		
			{
				case ORDERED_NODE_SNAPSHOT_TYPE:
				case UNORDERED_NODE_SNAPSHOT_TYPE:
				case ORDERED_NODE_ITERATOR_TYPE:
				case UNORDERED_NODE_ITERATOR_TYPE:
				case FIRST_ORDERED_NODE_TYPE:
					break;
				default:
					hr = GenerateCachedStylesheet(m_sExpression);

			}
		}

		if(m_pProcessor)
		{
			hr =transformByStylesheet(contextNode,pResult);
		}
		else
		{
			CComPtr<IXMLDOMNodeList> pNl;
			CComBSTR sNewNS;
			m_pResolver->lookupNamespaceURI(NULL,&sNewNS);
			if(!!sNewNS)
			{
				CComPtr<IXMLDOMDocument>  pDoc;
				contextNode->get_ownerDocument(&pDoc);
				CComQIPtr<IXMLDOMDocument2> pDoc2(pDoc);
				if(pDoc2)
				{
					CComVariant vOldNS;
					pDoc2->getProperty(sNsPropName,&vOldNS);
					{
						CComVariant vNewNS(sNewNS);
						pDoc2->setProperty(sNsPropName,vNewNS);
						contextNode->selectNodes(m_sExpression,&pNl);
					}
					pDoc2->setProperty(sNsPropName,vOldNS);
				}
			}
			else
			{
				//don't bother with all that palaver above
				//	if there are no namespaces to set.
				contextNode->selectNodes(m_sExpression,&pNl);
			}
			CComQIPtr<IXMLDOMSelection> pSelection = pNl;
			
			hr = pResult->parseSelection(pSelection);
		}
		pResult->QueryInterface(IID_IXPathResult,(void**)retVal);
		::safeRelease(pResult);
	}
	return hr;
}

const UINT g_msgXPathBeginTransform = WM_APP + 1;
const UINT g_msgXPathTestReadyState = WM_APP + 2;
#define TRACK_TRANSFORM_PHASES 1
#undef TRACK_TRANSFORM_PHASES
HRESULT CXPathExpression::transformByStylesheet(IXMLDOMNode* pContextNode, CComObject<CXPathResult>* pResult)
{
	HRESULT hr = S_OK;

	if (S_OK == hr)
	{
		pContextNode->AddRef();
		hr = m_pProcessor->put_input(CComVariant(pContextNode));

			//create a helper object to store the nodes
			//released after fetching nodeset back out of it.

		long lReady;

		hr = m_pProcessor->get_readyState(&lReady);
		
		if(lReady==READYSTATE_UNINITIALIZED)
		{
			hr = E_NOTINITIALISED;
		}
		else
		{
			if(m_pHelper) {
				m_pHelper->putResult(pResult);
				m_pProcessor->addObject(m_pHelper, g_sHelperURI);
			}
			VARIANT_BOOL bDone = false;

			MSG msg;
			// force msg-q to be created just in case, NOP otherwise
			//PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
			// msg to myself to do work
			DWORD dwThread = GetCurrentThreadId();
			PostThreadMessage(dwThread,g_msgXPathTestReadyState, 0, 0);
			// msg-pump
			while (PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE|PM_NOYIELD))
			{
				if(msg.message == WM_TIMER)
					break;

				if (msg.hwnd == NULL && msg.message == g_msgXPathBeginTransform)
				{
#ifdef TRACK_TRANSFORM_PHASES
					ATLTRACE("processor transform initiating\n");
#endif
					hr = m_pProcessor->transform(&bDone);
#ifdef TRACK_TRANSFORM_PHASES
					ATLTRACE("processor transform initiated\n");
#endif
					
					if(SUCCEEDED(hr))
					{
						PostThreadMessage(dwThread,g_msgXPathTestReadyState, 0, 0);
					}
				}
				else if (msg.hwnd == NULL && msg.message == g_msgXPathTestReadyState)
				{
					hr = m_pProcessor->get_readyState(&lReady);
					if(SUCCEEDED(hr))
					{
						if(lReady==READYSTATE_LOADED)
						{
#ifdef TRACK_TRANSFORM_PHASES
							ATLTRACE("processor transform loaded - ready to transform\n");
#endif
							PostThreadMessage(dwThread,g_msgXPathBeginTransform, 0, 0);
						}
						else if(lReady==READYSTATE_COMPLETE)
						{
#ifdef TRACK_TRANSFORM_PHASES
							ATLTRACE("processor transform async complete\n");
#endif
							break;
						}
						else if(lReady==READYSTATE_UNINITIALIZED)
						{
#ifdef TRACK_TRANSFORM_PHASES
							ATLTRACE("processor transform async not ready\n");
#endif
							hr = E_PENDING;
							break;
						}
						else
						{
#ifdef TRACK_TRANSFORM_PHASES
							ATLTRACE("processor transform running async\n");
#endif
							PostThreadMessage(dwThread,g_msgXPathTestReadyState, 0, 0);
						}
					}
				}
				else
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}
			}
			CComVariant vt;
			m_pProcessor->get_output(&vt);
			vt.Clear();
			m_pProcessor->addObject(NULL,g_sHelperURI);
			m_pProcessor->put_output(vt);
			m_pProcessor->reset();
		}
		VARIANT vtNULL;
		vtNULL.vt = VT_NULL;
		m_pProcessor->put_input(vtNULL);
		::safeRelease(pContextNode);

	}
	return hr;
}

HRESULT CXPathExpression::QualifyUnqualifiedFunctions(const BSTR bsExpr, BSTR * bsModifiedExpr) const
{
	HRESULT hr = S_FALSE;
	CAtlREMatchContext<CAtlRECharTraitsW> mc;
	//char * psExpr = _com_util::ConvertBSTRToString(bsExpr);
	CStringW psExpr = bsExpr;

	/*
	* [TODO]
	* Loop through as many times as we need to
	*/

	CStringW sNewExpr;
	const CAtlREMatchContext<CAtlRECharTraitsW>::RECHAR* szNext = psExpr;

	while (g_re.Match(szNext, &mc))
	{

		/*
		* Step through the functions that we have found
		*/

		/*
		* The result of parsing with the regular expression will be two groups. The
		* way it is structured means that only one of the groups will contain meaningful
		* results at any one time. We can tell which one it is when we retrieve the start
		* and end values.
		*
		* If the first group contains something then we have a core XPath function, or a namespace
		* qualified function. If it's the second group then we have a non-namespace qualified function.
		*/

		for (UINT nGroupIndex = 0; nGroupIndex < mc.m_uNumGroups; ++nGroupIndex)
		{
			const CAtlREMatchContext<CAtlRECharTraitsW>::RECHAR* szStart = 0;
			const CAtlREMatchContext<CAtlRECharTraitsW>::RECHAR* szEnd = 0;
			mc.GetMatch(nGroupIndex, &szStart, &szEnd);

			if (szStart && szEnd)
			{
				ptrdiff_t nLeadIn = szStart - szNext;
				ptrdiff_t nLength = szEnd - szStart;

				/*
				* Now that we have the start and end position of
				* the function name, and its length, we can copy
				* all of the characters that precede the function
				* name
				*/

  				sNewExpr.Append(szNext, nLeadIn);
				szNext += nLeadIn;

				/*
				* Next, if we have found a core function, or a function which is
				* already namespace qualified, then we just copy it straight over
				*/

				if (nGroupIndex < 2)
				{
					sNewExpr.Append(szNext, nLength);
				}
				else if (nGroupIndex < 4)
				{
					sNewExpr.Append(szNext, nLength);
					//Although nothing has been changed, qualified expressions exist.
					//	so the stylesheet method needs to be used.
					hr = S_OK;
				}

				/*
					* Otherwise we have a non core function with no namespace prefix ...
					*/

				else
				{
					/*
						* ... so we prepend the default namespace prefix (which will be wired
						* to the default function library)
						*/

					sNewExpr.Append(L"xportDOM3XPathDefNSPrefix:");
					sNewExpr.Append(szNext, nLength);
					hr = S_OK;
				}
				szNext += nLength;

			}//if ( this group actually returned anything )
		}// for ( each match )
	}//while ( mapped function was found )

	/*
	* Pick up any remaining text, and then assign the result
	*/

	sNewExpr.Append(szNext);
	*bsModifiedExpr = sNewExpr.AllocSysString();
	return hr;
}

HRESULT CXPathExpression::GenerateCachedStylesheet(const CComBSTR & bstrQualifiedExpression)
{
	//clone the global variable into pMSXMLXSLT and select
	//	this node from that
	//	otherwise we can't be multi-threaded.

	IXMLDOMNode * pDocAsNode = NULL;

	HRESULT hr = g_pXSL->cloneNode(VARIANT_TRUE,&pDocAsNode);

	if(SUCCEEDED(hr))
	{
		IXMLDOMDocument2 * pMSXMLXSLT = NULL;
		hr = pDocAsNode->QueryInterface(IID_IXMLDOMDocument2,(void**)&pMSXMLXSLT);
		//create the value-of node
		if(SUCCEEDED(hr))
		{
			IXMLDOMNode * pValueOfNode = NULL;
			CComBSTR sName,sNamespace;
			sName.LoadString(IDS_QueryNodeName);
			sNamespace.LoadString(IDS_NSURI_XSLT);
			hr = pMSXMLXSLT->createNode
				(
					CComVariant((long)NODE_ELEMENT),
					sName,
					sNamespace,
					&pValueOfNode
				);

			if(SUCCEEDED(hr))
			{
				
				//The value-of node needs to be an element for setattribute.
				CComPtr<IXMLDOMElement> pExprEl;
				hr = pValueOfNode->QueryInterface(IID_IXMLDOMElement,(void**)&pExprEl);

				if(SUCCEEDED(hr))
				{
					//Get the xsl:template element, Into this we will insert 
					//	the xsl:value-of element.
					CComPtr<IXMLDOMNode> pTemplateNode;
					CComBSTR sXPath;
					sXPath.LoadString(IDS_TemplatePath);
					hr = pMSXMLXSLT->selectSingleNode(sXPath,&pTemplateNode);

					if(SUCCEEDED(hr))
					{
						//insert value-of node then release temp.
						hr = pTemplateNode->appendChild(pExprEl,NULL);
						pTemplateNode.Release();
						if(SUCCEEDED(hr))
						{

							//Put the Namespace declaration into the value-of element,
							//	this will be done by NSResolver.
							CComBSTR bsNSList;
							m_pResolver->lookupNamespaceURI(NULL,&bsNSList);
							//Add the namespace declaration for the default 
							//	function library.
							bsNSList += "xmlns:xportDOM3XPathDefNSPrefix='http://www.x-port.net/namespaces/DOM3XPath'";
							char * sNSList = _com_util::ConvertBSTRToString(bsNSList);
							bsNSList.Empty();
							CXPathExpression::parseAttributeString(sNSList,pExprEl);
							delete [] sNSList;


							

							//set @select with the Xpath expression

							//Create the select attribute.
								//encapsulate the expr in getData
								//getData replaces getNodeset and takes a VARIANT argument
								//	this allows us to treat all queries in the same way
								//"xportSecretNamespaceDoNotUSE:getData(%s)"
							CComBSTR sSelectExpr;
							sSelectExpr.LoadString(IDS_ExprWrapper);
							
							wchar_t * sWrappedExpr = new wchar_t[(bstrQualifiedExpression.Length() + sSelectExpr.Length())];
							swprintf(sWrappedExpr,sSelectExpr,bstrQualifiedExpression);
							//poke it in to the attribute.
							hr = pExprEl->setAttribute(L"select",CComVariant(sWrappedExpr));
							delete [] sWrappedExpr;
							//No more use for ExprEl.
							pExprEl.Release(); //This should return 0;
							if(SUCCEEDED(hr))
							{
								CXSLTemplate oTemplate;
								CComPtr<IXMLDOMNode> pFTNode;
								if(g_FTXMLDOM)
								{
									CComPtr<IXMLDOMElement> pMSXMLXSLTDE;
									pMSXMLXSLT->get_documentElement(&pMSXMLXSLTDE);
									hr = g_FTXMLDOM->importNode(pMSXMLXSLTDE,VARIANT_TRUE,&pFTNode);
								}
								//At this point there are 2 references to pMSXML...
								//ie. the var itself, and pDocAsNode.
								hr = oTemplate.m_pXSLTemplate->putref_stylesheet(pFTNode);
								//At this point there are 4 references, the two mentioned in
								//	the previous comment, plus one for the putRef,  but 
								//	where is the other??

								if(SUCCEEDED(hr))
								{	

									hr = oTemplate.m_pXSLTemplate->createProcessor(&m_pProcessor);
//The problem that required this following line seems to have fixed itself.
//This means that there will be no more leaks from this location.										
//										m_pProcessor->AddRef();
									if(SUCCEEDED(hr) && !m_pHelper) {
										CComObject<CXPathHelper>::CreateInstance(&m_pHelper);
										if(m_pHelper)
											m_pHelper->AddRef();
										else
											hr = E_FAIL;
									}
								}
							}
						}
					}
				}
			}
			::safeRelease(pValueOfNode);
		}
		::dbg_safeRelease(pMSXMLXSLT);
	}
	::dbg_safeRelease(pDocAsNode);
	return hr;
}

HRESULT CXPathExpression::initialise(BSTR bsExpr, IXPathNSResolver * pResolver)
{
	ATLTRACE("initialising CXPathExpression\n");
	HRESULT hr = S_OK;

	if(!bsExpr)
	{
		//Can't make an expression without an XPath
		hr = E_NOXPATH;
	}
	else
	{

		//store the resolver.
		m_pResolver = pResolver;
		CComBSTR bstrQualifiedExpression;
		HRESULT _hr = QualifyUnqualifiedFunctions(bsExpr,&bstrQualifiedExpression);
		if(S_OK == _hr)
		{
			hr = GenerateCachedStylesheet(bstrQualifiedExpression);
		}
		else
		{
			//Without any modified functions to call, there is no need
			// to use the cached stylesheet.  Just use select(Single)Node(s) when
			//	required.
			m_sExpression.Attach(bstrQualifiedExpression.Detach());
		}
	}
	ATLTRACE("CXPathExpression initialisation done.\n");
	return hr;

}

void CXPathExpression::parseAttributeString( char  * const sNSList,IXMLDOMElement * pExprEl)
{
	//splits a string in the format - x="y" z="q"
	//	into attr-value pairs and inserts them into 
	//	the provided element.
	
	char * pcEq;	
	char * pcQuot;
	char * curs = sNSList;
	//char * sNSList = _com_util::ConvertBSTRToString(sNSDecl);
	char cQuot;
	CComBSTR sNSPref;
	CComBSTR sNSURI;
	
	//curs = sNSList;
	while(*curs)
	{
		while(*curs==' ')
		{
			if(*(++curs) == NULL)
			{
				break;
			}
		}

		if(*curs == NULL)
		{
			break;
		}

		pcEq = strchr(curs,'=');
		*pcEq = NULL;
		sNSPref = CComBSTR(curs);
		*pcEq = '=';

		cQuot = pcEq[1];
		curs = &pcEq[2]; //0='=' 1 = quote
		pcQuot = strchr(curs,cQuot);
		*pcQuot = NULL;
		sNSURI = CComBSTR(curs);
		*pcQuot = cQuot;
		curs = &pcQuot[1];
		CComVariant vtNSURI(sNSURI);
		pExprEl->setAttribute(sNSPref,vtNSURI);
		sNSPref.Empty();
		sNSURI.Empty();
		vtNSURI.Clear();

	}
}



STDMETHODIMP CXPathExpression::addFunctionLibrary(IDispatch *pObj, BSTR sURI,BOOL bIsDefaultLibrary)
{
	//TODO. keep a list of these so that i can release them when I close.

	HRESULT hr;
	if(!m_pProcessor)
	{
		hr =  E_FAIL;
	}
	else
	{
		hr = m_pProcessor->addObject(pObj,sURI);
		if(bIsDefaultLibrary)
		{
			hr = m_pProcessor->addObject(pObj,L"http://www.x-port.net/namespaces/DOM3XPath");
		}
	}
	return hr;
}

//XSLTemplate Wrapper

CXSLTemplate::CXSLTemplate()
{
	HRESULT hr;

	hr = CoInitialize(NULL);
	if(SUCCEEDED(hr))
		m_pXSLTemplate.CoCreateInstance(CLSID_XSLTemplate60,0,CLSCTX_INPROC_SERVER);
}

CXSLTemplate::~CXSLTemplate()
{
	ATLTRACE("Destroying CXSLTemplate\n");
	CoUninitialize();
}

#ifdef COUNT_EXPRESSION
	unsigned long CXPathExpression::sm_Count = 0;	
#endif